package com.qipay.withdraw.service.impl;

import com.qipay.baas.model.Banks;
import com.qipay.baas.model.Cp;
import com.qipay.baas.model.CpWithdrawTradlog;
import com.qipay.baas.model.Order;
import com.qipay.cp.CpService;
import com.qipay.cpaccount.CpAccountService;
import com.qipay.mapper.CpAccountMapper;
import com.qipay.mapper.CpWithdrawTradlogMapper;
import com.qipay.order.service.OrderService;
import com.qipay.pay.entity.CpPayUnionTradeQueryRequest;
import com.qipay.scalar.type.BankEnum;
import com.qipay.security.jwt.JWTUser;
import com.qipay.sp.SpService;
import com.qipay.sp.excutor.ExcutorFactory;
import com.qipay.withdraw.config.WithdrawOperationEnum;
import com.qipay.withdraw.config.WithdrawResultEnum;
import com.qipay.withdraw.config.WithdrawStateEnum;
import com.qipay.withdraw.config.WithdrawTypeEnum;
import com.qipay.withdraw.entity.CpBalanceQueryRequest;
import com.qipay.withdraw.service.WithdrawService;
import com.qipay.withdraw.service.entity.CpWithdrawRequest;
import io.jsonwebtoken.lang.Assert;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Creator: Yao
 * Date:    2017/9/27
 * For:
 * Other:
 */
@Service
public class WithdrawServiceImpl implements WithdrawService {

    protected final Log logger = LogFactory.getLog(this.getClass());

    public static final String WITHDRAW_PARAM_CARD_NO = "card_no";
    public static final String WITHDRAW_PARAM_BANK_NO = "bank_no";
    public static final String WITHDRAW_PARAM_BANK_CODE = "bank_code";
    public static final String WITHDRAW_PARAM_PAYEE = "payee";
    public static final String WITHDRAW_PARAM_ID_CARD_NO = "id_card_no";
    public static final String WITHDRAW_PARAM_MOBILE = "mobile";

    @Autowired
    CpWithdrawTradlogMapper withdrawMapper;

    @Autowired
    CpService cpService;

    @Autowired
    CpAccountService cpAccountService;

    @Autowired
    OrderService orderService;

    @Autowired
    SpService spService;

    @Autowired
    WithdrawService withdrawService;

    @Autowired
    CpAccountMapper cpAccountMapper;

    @Override
    public CpWithdrawTradlog get(Long withdrawId) {
        return withdrawMapper.selectByPrimaryKey(withdrawId);
    }

    @Override
    @Transactional
    public int save(CpWithdrawTradlog withdraw) {

        // check
        Assert.notNull(withdraw);

        // save
        return withdrawMapper.insert(withdraw);
    }

    @Override
    @Transactional
    public int update(CpWithdrawTradlog withdraw) {

        // check
        Assert.notNull(withdraw);

        // save
        return withdrawMapper.updateByPrimaryKey(withdraw);
    }

    @Override
    public long count(Long cpId,String orderNo,Byte state, String from, String to) {
        return withdrawMapper.count(cpId,orderNo,state, from, to);
    }

    @Override
    public long count(Long cpId) {
        return withdrawMapper.countCpAmount(cpId);
    }

    @Override
    public long countApply() {
        long num = withdrawMapper.countApply();
        withdrawMapper.updateRemind();
        //num
        return num;
    }

    @Override
    public CpWithdrawTradlog countInfo(Long cpId,String orderNo,Byte state, String from, String to) {
        return withdrawMapper.countInfo(cpId,orderNo,state, from, to);
    }

    @Override
    public List<CpWithdrawTradlog> list(Long cpId,String orderNo,Byte state, String from, String to, Integer p, Integer pz) {

        // init
        p = (null == p || p <= 0) ? 1 : p;
        pz = (null == pz || pz <= 0) ? 10 : pz;

        // query
        return withdrawMapper.list(cpId,orderNo, state ,from, to, (p - 1) * pz, pz);
    }

    @Override
    public List<CpWithdrawTradlog> list(String from, String to) {
        return withdrawMapper.listByTime(from, to);
    }

    @Override
    public List<CpWithdrawTradlog> list(Byte state) {
        return withdrawMapper.listByState(state);
    }

    @Override
    public void updateState(Long id) {
        withdrawMapper.updateState(id);
    }

    @Override
    public CpWithdrawTradlog findByOutTradeNo(String outTradeNo) {
        return withdrawMapper.findByOutTradeNo(outTradeNo);
    }

    @Override
    public List<CpWithdrawTradlog> listByCpId(Long cpId, Byte state) {
        return withdrawMapper.listByCpId(cpId,state);
    }

    @Override
    public List<Banks> findSubbranch(String province, String city, String bank) {
        return withdrawMapper.findSubbranch(province,city,bank);
    }


    @Override
    @Transactional
    public void fail(Long id, String outTradeNo, String error) {

        withdrawMapper.fail(id, outTradeNo, error);
    }

    @Override
    @Transactional
    public void finish(Long id, String outTradeNo) {
        withdrawMapper.success(id, outTradeNo);
    }

    @Override
    public Map<String, Object> withdrawQuery(CpPayUnionTradeQueryRequest entity, JWTUser loggedUser) throws Exception {
        org.springframework.util.Assert.notNull(loggedUser, "找不到登录信息");
        org.springframework.util.Assert.notNull(entity, "请输入交易信息");
        org.springframework.util.Assert.notNull(entity.getMchId(), "请输入商户号");
        org.springframework.util.Assert.isTrue(StringUtils.equals(entity.getMchId(), loggedUser.getUsername())
                , "输入的商户号与登录信息不一致");
        org.springframework.util.Assert.isTrue(entity.getOutTradeNo() !=null ,
                "请输入CP订单号");
        org.springframework.util.Assert.notNull(entity.getNonceStr(), "请输入nonce_str");

        CpWithdrawTradlog cpWithdrawTradlog = withdrawService.findByOutTradeNo(entity.getOutTradeNo());

        Assert.notNull(cpWithdrawTradlog, "找不到相应订单信息");
//        PAYING((byte) 0, "支付中"),
//                SUCCESS((byte) 2, "成功"),
//                FAIL((byte) 3, "失败");

        Map<String, Object> result = new HashMap<>();
        String trade_state = "FAIL";
        if(cpWithdrawTradlog.getState()==99||cpWithdrawTradlog.getState()==0)trade_state = "PAYING";
        else if(cpWithdrawTradlog.getState()==2)trade_state = "SUCCESS";

        result.put("result_code","0");
        result.put("trade_state",trade_state);
        result.put("message",trade_state.equals("FAIL")?cpWithdrawTradlog.getRemark():"");
        if(trade_state.equals("SUCCESS")){
            result.put("out_trade_no",cpWithdrawTradlog.getOutTradeNo());
            result.put("transaction_id",cpWithdrawTradlog.getTradeNo());
            result.put("total_fee",cpWithdrawTradlog.getAmount());
        }

        return result;
    }

    @Transactional
    @Override
    public synchronized Map<String, Object> withdraw(CpWithdrawRequest entity, JWTUser loggedUser) throws Exception {

        Assert.notNull(loggedUser, "找不到登录信息");
        Assert.notNull(entity, "请输入交易信息");
        Assert.notNull(entity.getMchId(), "请输入商户号");
        Assert.isTrue(StringUtils.equals(entity.getMchId(), loggedUser.getUsername())
                , "输入的商户号与登录信息不一致");
        Assert.notNull(entity.getOutTradeNo(), "请输入CP订单号");
        Assert.notNull(entity.getTotalFee(), "请输入总金额");
        Assert.isTrue(entity.getTotalFee() >= 10000, "总金额必须大于100");
        Assert.notNull(entity.getWithdrawType(), "请输入withdraw_type");
        Assert.isTrue(WithdrawTypeEnum.contains(entity.getWithdrawType()), "暂不支持该种提现");
        Assert.notNull(entity.getNonceStr(), "请输入nonce_str");
        Assert.notNull(entity.getIdCardNo(), "请输入id_card_no");
        Assert.notNull(entity.getPayee(), "请输入payee");
        Assert.notNull(entity.getMobile(), "请输入mobile");
        Assert.notNull(entity.getCardNo(), "请输入card_no");
        Assert.notNull(entity.getBankCode(), "请输入bank_code");
        Assert.isTrue(BankEnum.contains(entity.getBankCode()), "暂不支持该银行");
        Assert.notNull(entity.getBankName(), "请输入bank_name");
        Assert.notNull(entity.getSubBankName(), "请输入sub_bank_name");
        Assert.notNull(entity.getProvince(), "请输入province");
        Assert.notNull(entity.getPayee(), "请输入payee");
        Assert.notNull(entity.getIdCardNo(), "请输入id_card_no");
        Assert.notNull(entity.getMobile(), "请输入mobile");

        //组装信息
        CpWithdrawTradlog cpWithdrawTradlog = withdrawService.createWithdrawOrder(entity);
        //检验余额以及可行性检验
        cpWithdrawTradlog = checkCpAccount(cpWithdrawTradlog);
        //保存
        save(cpWithdrawTradlog);

        //此处改动备注，原来是代付渠道吧，具体没看，现在没有用
        //withdrawService.actualWithdraw(entity, cpWithdrawTradlog);
        Map<String, Object> result = new HashMap<>();
        int result_code = cpWithdrawTradlog.getState()==99?0:cpWithdrawTradlog.getState();
        result.put("result_code", result_code);
        result.put("pay_result",
                WithdrawResultEnum.valueOf((byte)result_code)==null?"FAIL":WithdrawResultEnum.valueOf((byte)result_code));
        if(result_code!=0)result.put("message", cpWithdrawTradlog.getRemark());
        return result;
    }
    /**
     * 验证余额是否充足,并且不能过快（也就是上一笔必须扣款完成）
     */
    public CpWithdrawTradlog checkCpAccount(CpWithdrawTradlog cpWithdrawTradlog){
        CpWithdrawTradlog c = withdrawService.findByOutTradeNo(cpWithdrawTradlog.getOutTradeNo());
        Assert.isNull(c,"订单号重复");

//        CpAccount cpAccount = cpAccountMapper.selectByPrimaryKey(cpWithdrawTradlog.getCpId());
        Long orderTotal = orderService.sumOrderByCp(cpWithdrawTradlog.getCpId());
        Long widthdrawTotal = withdrawService.count(cpWithdrawTradlog.getCpId());
        //此处不判断垫资还是可用，方便下游对接，只要余额够就行，简单粗暴
        if(orderTotal-widthdrawTotal >= cpWithdrawTradlog.getAmount()){
            cpWithdrawTradlog.setState(WithdrawStateEnum.APPLY.getState());
        }else{
            cpWithdrawTradlog.setState(WithdrawStateEnum.UN_PASS.getState());
            cpWithdrawTradlog.setRemark("可用余额不足");
        }
        return cpWithdrawTradlog;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public CpWithdrawTradlog createOrder(CpWithdrawRequest entity, JWTUser loggedUser) {
        //暂时不检测是否开通代付服务，反正都是手动审核
//        CpChannel cpChannel = cpChannelService.getByCpIdAndChannelType(loggedUser.getId(),
//                ChannelTypeEnum.WITHDRAW.getType());
//        Assert.notNull(cpChannel, "该CP没有开通" + ChannelTypeEnum.WITHDRAW.getInfo() + "服务");
//        Assert.isTrue(cpChannel.getState() == 0, "该CP的" + ChannelTypeEnum.WITHDRAW.getInfo() + "服务已经被禁用");
//        Assert.isTrue(cpService.isIPValid(loggedUser.getId(), entity.getMchCreateIp(),true)
//                , "非法ip值");
        CpWithdrawTradlog cpWithdrawTradlog = withdrawService.createWithdrawOrder(entity);
        return cpWithdrawTradlog;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public CpWithdrawTradlog actualWithdraw(CpWithdrawRequest entity, CpWithdrawTradlog cpWithdrawTradlog)
            throws Exception {
        Order order = orderService.getByTradeNo(cpWithdrawTradlog.getTradeNo());

        boolean withdrawResult = withdrawFromBank(order, entity);

        if (withdrawResult) {
            setWithdrawSuccess(cpWithdrawTradlog);
        } else {
            setWithdrawFail(cpWithdrawTradlog);
        }

        return cpWithdrawTradlog;
    }

    @Transactional
    @Override
    public CpWithdrawTradlog createWithdrawOrder(CpWithdrawRequest entity) {

        WithdrawTypeEnum withdrawTypeEnum = WithdrawTypeEnum.valueOf(entity.getWithdrawType());
        Cp cp = cpService.getOneById(Long.parseLong(entity.getMchId()));
        int fee = caculateWithrawFee(entity.getTotalFee(), cp, withdrawTypeEnum);

//        order = new Order();
//        order.setCpId(Long.parseLong(entity.getMchId()));
//        order.setChannelType(ChannelTypeEnum.WITHDRAW.getType());
//        order.setBody(withdrawTypeEnum.getInfo());
//        order.setAmount(entity.getTotalFee());
//        order.setFeeRecharge(new BigDecimal(0));
//        order.setState(OrderStateEnum.NOTPAY.getType());
//        order.setTradeNo(orderService.createOrder());
//        order.setOutTradeNo(entity.getOutTradeNo());
//        order.setNotifyUrl("");
//        order.setAttach(entity.getAttach());
//        order.setCreateTime(new Date());
//        order.setSettleStatus(OrderSettleStateEnum.NOTSETTLE.getType());
//        order.setAdvanceAmount(0);
//        order.setFee(fee);
//        order.setDisableAmount(0);
//        order.setActualAmount(0);
//
//        order = orderService.selectASp(order, ChannelTypeEnum.WITHDRAW);

//        orderService.insert(order);


        CpWithdrawTradlog cpWithdrawTradlog = new CpWithdrawTradlog();
        cpWithdrawTradlog.setCpId(Long.parseLong(entity.getMchId()));
        cpWithdrawTradlog.setAmount(Long.parseLong(entity.getTotalFee()+""));
        cpWithdrawTradlog.setFee(new BigDecimal(fee));
        cpWithdrawTradlog.setActualAmount(Long.parseLong((entity.getTotalFee()-fee) + ""));
        cpWithdrawTradlog.setType(withdrawTypeEnum.getType());
        cpWithdrawTradlog.setCardBank(BankEnum.valueOf(entity.getBankCode()).getInfo());
        cpWithdrawTradlog.setCardSubBank(entity.getSubBankName());
        cpWithdrawTradlog.setCardBankNo(entity.getBankNo());
        cpWithdrawTradlog.setCardNo(entity.getCardNo());
        cpWithdrawTradlog.setName(entity.getPayee());
        cpWithdrawTradlog.setMobile(entity.getMobile());
        cpWithdrawTradlog.setTradeNo(System.currentTimeMillis()+""+(int)(Math.random()*900+100));
        cpWithdrawTradlog.setOutTradeNo(entity.getOutTradeNo());
        cpWithdrawTradlog.setIdCardNo(entity.getIdCardNo());
        cpWithdrawTradlog.setAttach(entity.getAttach());
        cpWithdrawTradlog.setState(WithdrawStateEnum.APPLY.getState());
        cpWithdrawTradlog.setOperation(WithdrawOperationEnum.TRANSFER_INTEFACE.getOperation());
        cpWithdrawTradlog.setCreateTime(new Date());
        cpWithdrawTradlog.setProvince(entity.getProvince());
        cpWithdrawTradlog.setCity(entity.getCity());
        cpWithdrawTradlog.setState((byte)99);//99表示提交表单，减少金额统一处理

        return cpWithdrawTradlog;
    }

    @Transactional
    @Override
    public void setWithdrawSuccess(CpWithdrawTradlog cpWithdrawTradlog) {
        cpWithdrawTradlog.setState(WithdrawStateEnum.PAYED.getState());
        cpWithdrawTradlog.setFinishTime(new Date());
        update(cpWithdrawTradlog);

        orderService.setOrderSuccess(cpWithdrawTradlog.getTradeNo());
    }

    @Transactional
    @Override
    public void setWithdrawFail(CpWithdrawTradlog cpWithdrawTradlog) {
        cpWithdrawTradlog.setState(WithdrawStateEnum.REFUND.getState());
        cpWithdrawTradlog.setFinishTime(new Date());
        update(cpWithdrawTradlog);

        orderService.setOrderFail(cpWithdrawTradlog.getTradeNo());

        cpAccountService.setWithdrawFail(cpWithdrawTradlog);
    }

    @Override
    public Map<String, Object> queryBalance(CpBalanceQueryRequest entity, JWTUser loggedUser) throws Exception {
        Assert.notNull(loggedUser, "找不到登录信息");
        Assert.notNull(entity, "请输入交易信息");
        Assert.notNull(entity.getMchId(), "请输入商户号");
        Assert.isTrue(StringUtils.equals(entity.getMchId(), loggedUser.getUsername())
                , "输入的商户号与登录信息不一致");
        Assert.notNull(entity.getNonceStr(), "请输入nonce_str");
        Long orderTotal = orderService.sumOrderByCp(Long.parseLong(entity.getMchId()));
        Long widthdrawTotal = withdrawService.count(Long.parseLong(entity.getMchId()));

        Map<String, Object> result = new HashMap<>();
        result.put("result_code", "0");
        result.put("balance", orderTotal-widthdrawTotal);
        result.put("balance_available", orderTotal-widthdrawTotal);
        result.put("balance_advance", "0");
        result.put("balance_disable", "0");
        return result;
    }


    private int caculateWithrawFee(int amount, Cp cp, WithdrawTypeEnum withdrawTypeEnum) {
        int fee = 100;
        switch (withdrawTypeEnum) {
            case AVAILABLE:
                fee = cp.getCostWithdraw();
                break;
            case ADVANCE:
                fee = new BigDecimal(amount).multiply(cp.getFeeWithdrawAdvince()).intValue();
                fee = fee > 100 ? fee : 100;
                break;
        }

        return fee;
    }


    @Override
    public boolean withdrawFromBank(Order order, CpWithdrawRequest entity) throws Exception {
        Map<String, Object> extendOrderParams = new HashMap<>();
        extendOrderParams.put(WITHDRAW_PARAM_CARD_NO, entity.getCardNo());
        extendOrderParams.put(WITHDRAW_PARAM_BANK_CODE, entity.getBankCode());
        extendOrderParams.put(WITHDRAW_PARAM_BANK_NO, entity.getBankNo());
        extendOrderParams.put(WITHDRAW_PARAM_PAYEE, entity.getPayee());
        extendOrderParams.put(WITHDRAW_PARAM_ID_CARD_NO, entity.getIdCardNo());
        extendOrderParams.put(WITHDRAW_PARAM_MOBILE, entity.getMobile());

        boolean result = false;
        Map<String, Object> withdrawResult = excuteFactoryExcutor(order, extendOrderParams);
        if (withdrawResult != null && "0".equals(withdrawResult.get("result_code"))) {
            result = true;
        } else {
            result = queryWithdrawResult(order);
        }

        return result;
    }

    private Map<String, Object> excuteFactoryExcutor(Order order, Map<String, Object> extendOrderParams) {
        Map<String, Object> result = null;
        try {
            ExcutorFactory excutorFactory = spService.getFactory(order.getSpId());
            result = excutorFactory.createWithdrawExcutor().excutor(order, extendOrderParams);
        } catch (Exception ex) {
            ex.printStackTrace();
            logger.error(order.getTradeNo() + ":" + order.getChannelType().toString()
                    + " -- 执行工厂方法出错:" + ex.toString());
        }
        return result;
    }

//    @Override
//   public  List<CpWithdrawTradlog> listBy(Map<String, Object> params){return withdrawMapper.list(params);}




    private boolean queryWithdrawResult(Order order) throws Exception {
        ExcutorFactory excutorFactory = spService.getFactory(order.getSpId());
        Map<String, Object> result = excutorFactory.createWithdrawQueryExcutor().excutor(order);

        return result != null && "0".equals(result.get("result_code"));
    }


}
